version: 2.1

jobs:
  yarn-monorepo:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    resource_class: xlarge
    steps:
      - restore_cache:
          keys:
            - v2-cache-source-{{ .Branch }}-{{ .Revision }}
            - v2-cache-source-{{ .Branch }}
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "packages/")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - save_cache:
          key: v2-cache-source-{{ .Branch }}-{{ .Revision }}
          paths:
            - ".git"
      - restore_cache:
          keys:
            - v2-cache-yarn-install-{{ checksum "yarn.lock" }}
            - v2-cache-yarn-install
      - run:
          name: Install dependencies
          command: yarn --frozen-lockfile
      - save_cache:
          key: v2-cache-yarn-install-{{ checksum "yarn.lock" }}
          paths:
            - node_modules
            - packages/common-ts/node_modules
            - packages/contracts/node_modules
            - packages/contracts-bedrock/node_modules
            - packages/contracts-periphery/node_modules
            - packages/core-utils/node_modules
            - packages/data-transport-layer/node_modules
            - packages/drippie-mon/node_modules
            - packages/fault-detector/node_modules
            - packages/message-relayer/node_modules
            - packages/replica-healthcheck/node_modules
            - packages/sdk/node_modules
            - integration-tests/node_modules
      - run:
          name: Build monorepo
          command: yarn build
      - save_cache:
          key: v2-cache-yarn-build-{{ .Revision }}
          paths:
            - "."

  docker-publish:
    environment:
      DOCKER_BUILDKIT: 1
    parameters:
      docker_tags:
        description: Docker image tags as csv
        type: string
      docker_file:
        description: Path to Dockerfile
        type: string
      docker_context:
        description: Docker build context
        type: string
      target:
        description: Docker build target
        type: string
        default: ""
    docker:
      - image: circleci/buildpack-deps:stretch
    steps:
      - checkout
      - setup_remote_docker:
          version: 20.10.12
      - when:
          condition: <<parameters.target>>
          steps:
            - run:
                name: Build with context
                command: |
                  docker build \
                  $(echo -ne "<< parameters.docker_tags >>" | sed "s/,/\n/g" | sed -e 's/^/-t /' | tr '\n' ' ') \
                  -f <<parameters.docker_file>> \
                  --target <<parameters.target>> \
                  <<parameters.docker_context>>
      - unless:
          condition: <<parameters.target>>
          steps:
            - run:
                name: Build
                command: |
                  docker build \
                  $(echo -ne "<< parameters.docker_tags >>" | sed "s/,/\n/g" | sed -e 's/^/-t /' | tr '\n' ' ') \
                  -f <<parameters.docker_file>> \
                  <<parameters.docker_context>>
      - run:
          name: Publish
          command: |
            echo "$DOCKER_PASS" | docker login -u "$DOCKER_USERNAME" --password-stdin
            docker push <<parameters.docker_tags>>

  contracts-bedrock-tests:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    resource_class: medium
    steps:
      - restore_cache:
          keys:
            - v2-cache-yarn-build-{{ .Revision }}
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "(contracts-bedrock|hardhat-deploy-config)")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: lint
          command: yarn lint:check
          working_directory: packages/contracts-bedrock
      - run:
          name: slither
          command: yarn slither || exit 0
          working_directory: packages/contracts-bedrock
      - run:
          name: test
          command: yarn test
          working_directory: packages/contracts-bedrock
      - run:
          name: gas snapshot
          command: |
            forge --version
            forge snapshot --check
          working_directory: packages/contracts-bedrock
      - run:
          name: storage snapshot
          command: yarn storage-snapshot && git diff --exit-code .storage-layout
          working_directory: packages/contracts-bedrock
      - run:
          name: check go bindings
          command: make && git diff --exit-code
          working_directory: op-bindings

  js-lint-test:
    parameters:
      package_name:
        description: Package name
        type: string
      dependencies:
        description: Regex matching dependent packages
        type: string
        default: this-package-does-not-exist
    docker:
      - image: ethereumoptimism/ci-builder:latest
    resource_class: large
    steps:
      - restore_cache:
          keys:
            - v2-cache-yarn-build-{{ .Revision }}
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "(packages/<<parameters.package_name>>|packages/<<parameters.dependencies>>)")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: Lint
          command: yarn lint:check
          working_directory: packages/<<parameters.package_name>>
      - run:
          name: Test
          command: yarn test:coverage
          working_directory: packages/<<parameters.package_name>>

  bedrock-go-tests:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    steps:
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "op-(batcher|bindings|e2e|node|proposer)")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: lint op-bindings
          command: |
            golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell ./...
          working_directory: op-bindings
      - run:
          name: lint op-node
          command: |
            golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell ./...
          working_directory: op-node
      - run:
          name: lint op-proposer
          command: |
            golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell ./...
          working_directory: op-proposer
      - run:
          name: lint op-batcher
          command: |
            golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell ./...
          working_directory: op-batcher
      - run:
          name: lint op-e2e
          command: |
            golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell ./...
          working_directory: op-e2e
      - run:
          name: prep results dir
          command: mkdir -p /test-results
      - run:
          name: test op-node
          command: |
            gotestsum --junitfile /test-results/op-node.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
          working_directory: op-node
      - run:
          name: test op-proposer
          command: |
            gotestsum --junitfile /test-results/op-proposer.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
          working_directory: op-proposer
      - run:
          name: test op-batcher
          command: |
            gotestsum --junitfile /test-results/op-batcher.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
          working_directory: op-batcher
      - run:
          name: test op-e2e
          command: |
            gotestsum --junitfile /test-results/op-e2e.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
          working_directory: op-e2e
      - store_test_results:
          path: /test-results
      - run:
          command: echo "Done."

  bedrock-markdown:
    machine:
      image: ubuntu-2004:202111-02
    steps:
      - checkout
      - run:
          name: markdown lint
          command: |
            docker run -v `pwd`:/workdir davidanson/markdownlint-cli2:0.4.0 "op-node/README.md" "./specs/**/*.md" "#**/node_modules"
      - run:
          name: link lint
          command: |
            docker run --init -it -v `pwd`:/input lycheeverse/lychee --verbose --no-progress --exclude-loopback --exclude twitter.com --exclude-mail /input/README.md "/input/specs/**/*.md" "/input/meta/**/*.md" "/input/op-node/**/*.md" || exit 0

  fuzz-op-node:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    steps:
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "op-node")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: Fuzz
          command: make fuzz
          working_directory: op-node

  depcheck:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    steps:
      - restore_cache:
          keys:
            - v2-cache-yarn-build-{{ .Revision }}
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "packages/")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      # Note: The below needs to be manually configured whenever we
      # add a new package to CI.
      - run:
          name: Check contracts
          command: npx depcheck
          working_directory: packages/contracts
      - run:
          name: Check contracts-periphery
          command: npx depcheck
          working_directory: packages/contracts-periphery
      - run:
          name: Check core-utils
          command: npx depcheck
          working_directory: packages/core-utils
      - run:
          name: Check data-transport-layer
          command: npx depcheck
          working_directory: packages/data-transport-layer
      - run:
          name: Check sdk
          command: npx depcheck
          working_directory: packages/sdk
      - run:
          name: Check integration-tests
          command: npx depcheck
          working_directory: integration-tests

  go-lint-test-build:
    parameters:
      binary_name:
        description: Binary name to build
        type: string
      working_directory:
        description: Working directory
        type: string
      build:
        description: Whether or not to build the binary
        type: boolean
        default: true
      dependencies:
        description: Regex matching dependent packages
        type: string
        default: this-package-does-not-exist
    docker:
      - image: ethereumoptimism/ci-builder:latest
      - image: cimg/postgres:14.1
    steps:
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "(<<parameters.working_directory>>|<<parameters.dependencies>>)")
            echo $CHANGED
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: Lint
          command: golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell ./...
          working_directory: <<parameters.working_directory>>
      - store_test_results:
          path: /test-results
      - run:
          name: Test
          command: |
            mkdir -p /test-results
            gotestsum --junitfile /test-results/tests.xml
          working_directory: <<parameters.working_directory>>
      - when:
          condition:
            equal: [ true, <<parameters.build>> ]
          steps:
            - run:
                name: Build
                command: make <<parameters.binary_name>>
                working_directory: <<parameters.working_directory>>

  geth-tests:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    steps:
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(check-changed "l2geth")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: Test
          command: make test
          working_directory: l2geth

  integration-tests:
    machine:
      image: ubuntu-2004:202201-01
      docker_layer_caching: true
    environment:
      DOCKER_BUILDKIT: 1
    parallelism: 3
    steps:
      - checkout
      - run:
          name: Check if we should run
          command: |
            CHANGED=$(bash ./ops/docker/ci-builder/check-changed.sh "(l2geth|common-ts|contracts|core-utils|message-relayer|data-transport-layer|replica-healthcheck|sdk|batch-submitter|gas-oracle|bss-core|integration-tests)/")
            if [[ "$CHANGED" = "FALSE" ]]; then
              circleci step halt
            fi
      - run:
          name: Bring up the stack
          command: |
            docker-compose build --progress=plain
            docker-compose up -d --scale replica_healthcheck=1
          working_directory: ops
      - run:
          name: Wait for sequencer
          command: bash scripts/wait-for-sequencer.sh
          working_directory: ops
      - run:
          name: Run integration tests
          command: |
            circleci tests glob "../integration-tests/test/*.spec.ts" | circleci tests split | tee splits.txt
            docker-compose run integration_tests $(cat splits.txt)
          working_directory: ops
      - run:
          command: echo "Done."

  semgrep-scan:
    parameters:
      diff_branch:
        type: string
        default: develop
    environment:
      TEMPORARY_BASELINE_REF: << parameters.diff_branch >>
      SEMGREP_REPO_URL: << pipeline.project.git_url >>
      SEMGREP_BRANCH: << pipeline.git.branch >>
      SEMGREP_COMMIT: << pipeline.git.revision >>

      # Change job timeout (default is 1800 seconds; set to 0 to disable)
      SEMGREP_TIMEOUT: 3000

    docker:
      - image: returntocorp/semgrep
    resource_class: xlarge
    steps:
      - checkout
      - unless:
          condition:
            equal: [ "develop", << pipeline.git.branch >> ]
          steps:
            - run:
                # Scan changed files in PRs, block on new issues only (existing issues ignored)
                # Do a full scan when scanning develop, otherwise do an incremental scan.
                name: "Conditionally set BASELINE env var"
                command: |
                  echo 'export SEMGREP_BASELINE_REF=${TEMPORARY_BASELINE_REF}' >> $BASH_ENV
      - run:
          name: "Set environment variables" # for PR comments and in-app hyperlinks to findings
          command: |
            echo 'export SEMGREP_PR_ID=${CIRCLE_PULL_REQUEST##*/}' >> $BASH_ENV
            echo 'export SEMGREP_JOB_URL=$CIRCLE_BUILD_URL' >> $BASH_ENV
            echo 'export SEMGREP_REPO_NAME=$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME' >> $BASH_ENV
      - run:
          name: "Semgrep scan"
          command: semgrep ci

  go-mod-tidy:
    docker:
      - image: ethereumoptimism/ci-builder:latest
    steps:
      - checkout
      - run:
          name: "Go mod tidy"
          command: make mod-tidy && git diff --exit-code


workflows:
  main:
    jobs:
      - yarn-monorepo
      - js-lint-test:
          name: common-ts-tests
          package_name: common-ts
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: contracts-tests
          package_name: contracts
          dependencies: hardhat-deploy-config
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: core-utils-tests
          package_name: core-utils
          requires:
            - yarn-monorepo
      - contracts-bedrock-tests:
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: contracts-governance-tests
          package_name: contracts-governance
          dependencies: "(core-utils|sdk)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: contracts-periphery-tests
          package_name: contracts-periphery
          dependencies: "(contracts|contracts-bedrock|core-utils|hardhat-deploy-config)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: dtl-tests
          package_name: data-transport-layer
          dependencies: "(common-ts|contracts|core-utils)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: drippie-mon-tests
          package_name: drippie-mon
          dependencies: "(common-ts|contracts-periphery|core-utils|sdk)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: fault-detector-tests
          package_name: fault-detector
          dependencies: "(common-ts|contracts|core-utils|sdk)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: message-relayer-tests
          package_name: message-relayer
          dependencies: "(common-ts|core-utils|sdk)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: replica-healthcheck-tests
          package_name: replica-healthcheck
          dependencies: "(common-ts|core-utils)"
          requires:
            - yarn-monorepo
      - js-lint-test:
          name: sdk-tests
          package_name: sdk
          dependencies: "(contracts|core-utils)"
          requires:
            - yarn-monorepo
      - depcheck:
          requires:
            - yarn-monorepo
      - bedrock-go-tests
      - fuzz-op-node
      - bedrock-markdown
      - go-lint-test-build:
          name: batch-submitter-tests
          binary_name: batch-submitter
          working_directory: batch-submitter
          dependencies: bss-core
      - go-lint-test-build:
          name: proxyd-tests
          binary_name: proxyd
          working_directory: proxyd
      - go-lint-test-build:
          name: teleportr-tests
          binary_name: teleportr
          working_directory: teleportr
          dependencies: bss-core
      - go-lint-test-build:
          name: gas-oracle-tests
          binary_name: gas-oracle
          working_directory: gas-oracle
      - go-lint-test-build:
          name: indexer-tests
          binary_name: indexer
          working_directory: indexer
      - go-lint-test-build:
          name: bss-core-tests
          binary_name: bss-core
          working_directory: bss-core
          build: false
      - geth-tests
      - integration-tests
      - semgrep-scan
      - go-mod-tidy

  nightly:
    triggers:
      - schedule:
          cron: "0 10 * * *"
          filters:
            branches:
              only:
                - develop
    jobs:
      - yarn-monorepo
      - docker-publish:
          name: l2geth-release
          docker_file: l2geth/Dockerfile
          docker_tags: ethereumoptimism/l2geth:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: gas-oracle-release
          docker_file: gas-oracle/Dockerfile
          docker_tags: ethereumoptimism/gas-oracle:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: hardhat-node-release
          docker_file: ops/docker/hardhat/Dockerfile
          docker_tags: ethereumoptimism/hardhat-node:nightly
          docker_context: ops/docker/hardhat
          context:
            - optimism
      - docker-publish:
          name: go-builder-release
          docker_file: ops/docker/go-builder/Dockerfile
          docker_tags: ethereumoptimism/go-builder:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: js-builder-release
          docker_file: ops/docker/js-builder/Dockerfile
          docker_tags: ethereumoptimism/js-builder:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: proxyd-release
          docker_file: proxyd/Dockerfile
          docker_tags: ethereumoptimism/proxyd:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: l2geth-exporter-release
          docker_file: l2geth-exporter/Dockerfile
          docker_tags: ethereumoptimism/l2geth-exporter:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: op-exporter-release
          docker_file: op-exporter/Dockerfile
          docker_tags: ethereumoptimism/op-exporter:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: fault-detector-release
          docker_file: ops/docker/Dockerfile.packages
          docker_tags: ethereumoptimism/fault-detector:nightly
          docker_context: .
          target: fault-detector
          context:
            - optimism
      - docker-publish:
          name: drippie-mon-release
          docker_file: ops/docker/Dockerfile.packages
          docker_tags: ethereumoptimism/drippie-mon:nightly
          docker_context: .
          target: drippie-mon
          context:
            - optimism
      - docker-publish:
          name: message-relayer-release
          docker_file: ops/docker/Dockerfile.packages
          docker_tags: ethereumoptimism/message-relayer:nightly
          docker_context: .
          target: message-relayer
          context:
            - optimism
      - docker-publish:
          name: data-transport-layer-release
          docker_file: ops/docker/Dockerfile.packages
          docker_tags: ethereumoptimism/data-transport-layer:nightly
          docker_context: .
          target: data-transport-layer
          context:
            - optimism
      - docker-publish:
          name: integration-tests-release
          docker_file: ops/docker/Dockerfile.packages
          docker_tags: ethereumoptimism/integration-tests:nightly
          docker_context: .
          target: integration-tests
          context:
            - optimism
      - docker-publish:
          name: replica-healthcheck-release
          docker_file: ops/docker/Dockerfile.packages
          docker_tags: ethereumoptimism/replica-healthcheck:nightly
          docker_context: .
          target: replica-healthcheck
          context:
            - optimism
      - docker-publish:
          name: batch-submitter-service-release
          docker_file: batch-submitter/Dockerfile
          docker_tags: ethereumoptimism/batch-submitter-service:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: indexer-release
          docker_file: indexer/Dockerfile
          docker_tags: ethereumoptimism/indexer:nightly
          docker_context: .
          context:
            - optimism
      - docker-publish:
          name: teleportr-release
          docker_file: teleportr/Dockerfile
          docker_tags: ethereumoptimism/teleportr:nightly
          docker_context: .
          context:
            - optimism
