synapsecns/sanguine

View on GitHub
.github/workflows/solidity.yml

Summary

Maintainability
Test Coverage
name: Solidity

on:
  pull_request:
    paths:
      - 'packages/contracts-core/**'
      - 'packages/contracts-rfq/**'
      - '.github/workflows/solidity.yml'
      - 'packages/solidity-devops/**'
    branches-ignore:
      # Solidity workflows are irrelevant for the FE release branch
      - 'fe-release'
  push:
    paths:
      - 'packages/contracts-core/**'
      - 'packages/contracts-rfq/**'
      - 'packages/solidity-devops/**'
      - '.github/workflows/solidity.yml'

jobs:
  changes:
    needs: cancel-outdated
    runs-on: ubuntu-latest
    outputs:
      # Expose matched filters as job 'packages' output variable
      packages: ${{ steps.filter_solidity.outputs.changes }}
      package_count: ${{ steps.length.outputs.FILTER_LENGTH }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          # if any of these packages use submodules in the future, please uncomment this line
          # submodules: 'recursive'
      - uses: dorny/paths-filter@v3
        id: filter_solidity
        with:
          #  make sure to update run-goreleaser when adding a new package here
          # also add to the get-project-id step
          filters: |
            contracts-core: 'packages/contracts-core/**'
            contracts-rfq: 'packages/contracts-rfq/**'
            solidity-devops: 'packages/solidity-devops/**'
      - id: length
        run: |
          export FILTER_LENGTH=$(echo $FILTERED_PATHS | jq '. | length')
          echo "##[set-output name=FILTER_LENGTH;]$(echo $FILTER_LENGTH)"
        env:
          FILTERED_PATHS: ${{ steps.filter_solidity.outputs.changes }}

  docs:
    name: Deploy Docs
    runs-on: ubuntu-latest
    needs: changes
    if: ${{ needs.changes.outputs.package_count > 0 }}
    strategy:
      fail-fast: false
      matrix:
        package: ${{ fromJson(needs.changes.outputs.packages) }}
    env:
      WORKING_DIRECTORY: 'packages/${{matrix.package}}'
      VERCEL_TOKEN: '${{ secrets.VERCEL_TOKEN }}'
      VERCEL_ORG_ID: '${{ secrets.VERCEL_ORG_ID }}'
      NODE_ENV: 'production'
    steps:
      - uses: actions/checkout@v4
      - name: Setup NodeJS
        uses: ./.github/actions/setup-nodejs
        with:
          cache: 'npm'
          cache-path: ''
      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
        with:
          # TODO: get back to nightly once `forge doc` is working with it
          version: nightly-09fe3e041369a816365a020f715ad6f94dbce9f2
      - name: Install Vercel CLI
        run: npm install --global vercel@30.1.0
      - name: Get Project ID
        id: project_id
        # see: https://stackoverflow.com/a/75231888 for details
        run: |
          PROJECT_IDS=$(cat <<END
          {
            "contracts-core": "${{ secrets.VERCEL_CONTRACT_DOCS_PROJECT_ID}}",
            "contracts-rfq": "${{ secrets.VERCEL_CONTRACT_RFQ_DOCS_PROJECT_ID }}",
            "solidity-devops": "${{ secrets.VERCEL_DEVOPS_DOCS_PROJECT_ID }}"
          }
          END
          )
          TARGET_ID=$(echo $PROJECT_IDS | jq -r 'to_entries[] | select(.key=="${{ matrix.package }}") | .value')
          echo "##[set-output name=VERCEL_PROJECT_ID;]$(echo $TARGET_ID)"
      - name: Build Docs
        working-directory: 'packages/${{matrix.package}}'
        # https://github.com/orgs/vercel/discussions/3322#discussioncomment-6480458
        # TODO: dedupe vercel.package.json
        run: |
          forge doc
          cp vercel.package.json docs/package.json

      - name: Deploy (Prod)
        if: ${{ format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }}
        run: |
          vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
          vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
          vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} --prod
        env:
          VERCEL_PROJECT_ID: ${{ steps.project_id.outputs.VERCEL_PROJECT_ID}}
      - name: Deploy
        run: |
          vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
          vercel build --token=${{ secrets.VERCEL_TOKEN }}
          vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
        env:
          VERCEL_PROJECT_ID: ${{ steps.project_id.outputs.VERCEL_PROJECT_ID}}

  cancel-outdated:
    name: Cancel Outdated Jobs
    runs-on: ubuntu-latest
    steps:
      - id: skip_check
        if: ${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && !contains(github.event.head_commit.message, '[no_skip]') }}
        uses: fkirc/skip-duplicate-actions@v5
        with:
          cancel_others: 'true'
  slither:
    name: Slither
    if: ${{ needs.changes.outputs.package_count > 0 && needs.changes.outputs.packages != '["solidity-devops"]' }}
    # see https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/setting-up-code-scanning-for-a-repository
    runs-on: ubuntu-latest
    needs: changes
    strategy:
      fail-fast: false
      matrix:
        package: ${{ fromJson(needs.changes.outputs.packages) }}
        # Slither is irrelevant for solidity-devops, as it only contains devops scripts rather than deployed contracts
        exclude:
          - package: solidity-devops
    permissions:
      # always required
      security-events: write
      # only required for private repos
      actions: read
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 2
          submodules: 'recursive'

      - name: Setup NodeJS
        uses: ./.github/actions/setup-nodejs

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
        with:
          version: nightly

      # TODO: find a flag for this
      - name: Delete Untested Files
        working-directory: './packages/${{matrix.package}}'
        run: |
          rm -rf test/ || true
          rm -rf script/ || true

      - name: Build Contracts
        # TODO: unforunately, as of now concurrency needs to be 1 since if multiple instances of forge try to install the same version
        # of solc at the same time, we get "text file busy" errors. See https://github.com/synapsecns/sanguine/actions/runs/8457116792/job/23168392860?pr=2234
        # for an example.
        run: |
          npx lerna exec npm run build:slither --concurrency=1

      - name: Run Slither
        uses: crytic/slither-action@v0.3.0
        continue-on-error: true
        id: slither
        with:
          node-version: '${{steps.nvmrc.outputs.NVMRC}}'
          target: './packages/${{matrix.package}}'
          slither-config: './packages/${{matrix.package}}/slither.config.json'
          ignore-compile: true
          sarif: results.sarif
          solc-version: 0.8.17

      - name: Upload SARIF file
        if: ${{!github.event.repository.private}}
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: ./results.sarif

  coverage:
    name: Foundry Coverage
    runs-on: ubuntu-latest
    if: ${{ needs.changes.outputs.package_count > 0 }}
    needs: changes
    strategy:
      fail-fast: false
      matrix:
        package: ${{ fromJson(needs.changes.outputs.packages) }}
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Setup Node JS
        uses: ./.github/actions/setup-nodejs

      - name: Installing dependencies
        run: yarn install --immutable

      - name: Install lcov
        run: sudo apt-get update && sudo apt-get install -y lcov

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
        with:
          version: nightly

      - name: Run Foundry Tests
        working-directory: './packages/${{matrix.package}}'
        run: forge test -vvv

      # We skip only the coverage steps for solidity-devops before we establish a good coverage there
      - name: Run Foundry Coverage
        if: ${{ matrix.package != 'solidity-devops' }}
        working-directory: './packages/${{matrix.package}}'
        run: forge coverage --report lcov --report summary >> $GITHUB_STEP_SUMMARY
        # Limit the number of fuzz runs to speed up the coverage
        env:
          FOUNDRY_FUZZ_RUNS: 10

      # Some of the packages may want to exclude certain files from the coverage report (legacy code, scripts, tests)
      - name: Apply filters to coverage report
        if: ${{ matrix.package != 'solidity-devops' }}
        working-directory: './packages/${{matrix.package}}'
        run: npm run coverage:filter --if-present

      - name: Send Coverage (Codecov)
        if: ${{ matrix.package != 'solidity-devops' }}
        uses: Wandalen/wretry.action@v1.0.36
        with:
          action: codecov/codecov-action@v3
          current_path: './packages/${{matrix.package}}'
          with: |
            token: ${{ secrets.CODECOV }}
            fail_ci_if_error: true # optional (default = false)
            verbose: true # optional (default = false)
            flags: solidity
          attempt_limit: 5
          attempt_delay: 30000

  gas-diff:
    runs-on: ubuntu-latest
    name: Foundry Gas Diff
    if: ${{ needs.changes.outputs.package_count > 0 && needs.changes.outputs.packages != '["solidity-devops"]' }}
    needs: changes
    strategy:
      fail-fast: false
      matrix:
        package: ${{ fromJson(needs.changes.outputs.packages) }}
        # Gas diff is irrelevant for solidity-devops, as it only contains devops scripts rather than deployed contracts
        exclude:
          - package: solidity-devops
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Setup Node JS
        uses: ./.github/actions/setup-nodejs

      - name: Installing dependencies
        run: yarn install --immutable

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
        with:
          version: nightly

      - name: Run tests and generate gas report
        working-directory: './packages/${{matrix.package}}'
        # Run separate set of tests (no fuzzing) to get accurate average gas cost estimates
        # Note: we use `npm run` with `--if-present` flag, allows not to define a gas:bench script in every package
        # This is not natively supported by yarn yet, see: https://github.com/yarnpkg/yarn/pull/7159
        run: npm run gas:bench --if-present > "../../gas-report-${{ matrix.package }}.ansi"

      - name: Compare gas reports
        uses: Rubilmax/foundry-gas-diff@v3.18
        with:
          ignore: 'test/**/*'
          report: 'gas-report-${{ matrix.package }}.ansi'
          sortCriteria: avg
          sortOrders: desc
          summaryQuantile: 0.5
        id: gas_diff

      - name: Add gas diff to sticky comment
        if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          # delete the comment in case changes no longer impact gas costs
          delete: ${{ !steps.gas_diff.outputs.markdown }}
          message: ${{ steps.gas_diff.outputs.markdown }}

  size-check:
    name: Foundry Size Check
    runs-on: ubuntu-latest
    if: ${{ needs.changes.outputs.package_count > 0 && needs.changes.outputs.packages != '["solidity-devops"]' }}
    needs: changes
    strategy:
      fail-fast: false
      matrix:
        package: ${{ fromJson(needs.changes.outputs.packages) }}
        # Size check is irrelevant for solidity-devops, as it only contains devops scripts rather than deployed contracts
        exclude:
          - package: solidity-devops
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Setup Node JS
        uses: ./.github/actions/setup-nodejs

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
        with:
          version: nightly

      # This will run https://book.getfoundry.sh/reference/forge/forge-build#build-options
      - name: Run forge build --sizes
        run: |
          forge build --sizes
        working-directory: './packages/${{matrix.package}}'