@hyperlane-xyz/core
Version:
Core solidity contracts for Hyperlane
373 lines (331 loc) • 15.9 kB
YAML
name: Solidity Foundry Artifact Generation
on:
workflow_dispatch:
inputs:
product:
type: choice
description: 'product for which to generate artifacts; should be the same as Foundry profile'
required: true
options:
- "automation"
- "ccip"
- "functions"
- "keystone"
- "l2ep"
- "liquiditymanager"
- "llo-feeds"
- "operatorforwarder"
- "shared"
- "transmission"
- "vrf"
base_ref:
description: 'commit or tag to be used as base reference, when looking for modified Solidity files'
required: true
env:
FOUNDRY_PROFILE: ci
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
changes: ${{ steps.changes.outputs.sol }}
product_changes: ${{ steps.changes-transform.outputs.product_changes }}
product_files: ${{ steps.changes-transform.outputs.product_files }}
changeset_changes: ${{ steps.changes-dorny.outputs.changeset }}
changeset_files: ${{ steps.changes-dorny.outputs.changeset_files }}
steps:
- name: Checkout the repo
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- name: Find modified contracts
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
list-files: 'csv'
base: ${{ inputs.base_ref }}
predicate-quantifier: every
filters: |
ignored: &ignored
- '!contracts/src/v0.8/**/test/**'
- '!contracts/src/v0.8/**/tests/**'
- '!contracts/src/v0.8/**/mock/**'
- '!contracts/src/v0.8/**/mocks/**'
- '!contracts/src/v0.8/**/*.t.sol'
- '!contracts/src/v0.8/*.t.sol'
- '!contracts/src/v0.8/**/testhelpers/**'
- '!contracts/src/v0.8/testhelpers/**'
- '!contracts/src/v0.8/vendor/**'
other_shared:
- modified|added: 'contracts/src/v0.8/(interfaces/**/*.sol|*.sol)'
- *ignored
sol:
- modified|added: 'contracts/src/v0.8/**/*.sol'
- *ignored
product: &product
- modified|added: 'contracts/src/v0.8/${{ inputs.product }}/**/*.sol'
- *ignored
changeset:
- modified|added: 'contracts/.changeset/!(README)*.md'
# Manual transformation needed, because shared contracts have a different folder structure
- name: Transform modified files
id: changes-transform
shell: bash
run: |
if [ "${{ inputs.product }}" = "shared" ]; then
echo "::debug:: Product is shared, transforming changes"
if [[ "${{ steps.changes.outputs.product }}" == "true" && "${{ steps.changes.outputs.other_shared }}" == "true" ]]; then
echo "::debug:: Changes were found in 'shared' folder and in 'interfaces' and root folders"
echo "product_changes=true" >> $GITHUB_OUTPUT
echo "product_files=${{ steps.changes.outputs.product_files }},${{ steps.changes.outputs.other_shared_files }}" >> $GITHUB_OUTPUT
elif [[ "${{ steps.changes.outputs.product }}" == "false" && "${{ steps.changes.outputs.other_shared }}" == "true" ]]; then
echo "::debug:: Only contracts in' interfaces' and root folders were modified"
echo "product_changes=true" >> $GITHUB_OUTPUT
echo "product_files=${{ steps.changes.outputs.other_shared_files }}" >> $GITHUB_OUTPUT
elif [[ "${{ steps.changes.outputs.product }}" == "true" && "${{ steps.changes.outputs.other_shared }}" == "false" ]]; then
echo "::debug:: Only contracts in 'shared' folder were modified"
echo "product_changes=true" >> $GITHUB_OUTPUT
echo "product_files=${{ steps.changes.outputs.product_files }}" >> $GITHUB_OUTPUT
else
echo "::debug:: No contracts were modified"
echo "product_changes=false" >> $GITHUB_OUTPUT
echo "product_files=" >> $GITHUB_OUTPUT
fi
else
echo "product_changes=${{ steps.changes.outputs.product }}" >> $GITHUB_OUTPUT
echo "product_files=${{ steps.changes.outputs.product_files }}" >> $GITHUB_OUTPUT
fi
- name: Check for changes outside of artifact scope
uses: ./.github/actions/validate-artifact-scope
if: ${{ steps.changes.outputs.sol == 'true' }}
with:
sol_files: ${{ steps.changes.outputs.sol_files }}
product: ${{ inputs.product }}
gather-basic-info:
name: Gather basic info
if: ${{ needs.changes.outputs.product_changes == 'true' }}
runs-on: ubuntu-22.04
needs: [ changes ]
outputs:
foundry_version: ${{ steps.extract-foundry-version.outputs.foundry-version }}
steps:
- name: Checkout the repo
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
fetch-depth: 0
- name: Extract Foundry version
id: extract-foundry-version
uses: ./.github/actions/detect-solidity-foundry-version
with:
working-directory: contracts
- name: Copy modified changesets
if: ${{ needs.changes.outputs.changeset_changes == 'true' }}
run: |
mkdir -p contracts/changesets
files="${{ needs.changes.outputs.changeset_files }}"
IFS=",'
for changeset in $files; do
echo "::debug:: Copying $changeset"
cp $changeset contracts/changesets
done
- name: Generate basic info and modified contracts list
shell: bash
run: |
echo "Commit SHA used to generate artifacts: ${{ github.sha }}" > contracts/commit_sha_base_ref.txt
echo "Base reference SHA used to find modified contracts: ${{ inputs.base_ref }}" >> contracts/commit_sha_base_ref.txt
IFS=',' read -r -a modified_files <<< "${{ needs.changes.outputs.product_files }}"
echo "# Modified contracts:" > contracts/modified_contracts.md
for file in "${modified_files[@]}"; do
echo " - [$file](${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/$file)" >> contracts/modified_contracts.md
echo "$file" >> contracts/modified_contracts.txt
done
- name: Upload basic info and modified contracts list
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
timeout-minutes: 2
continue-on-error: true
with:
name: tmp-basic-info
path: |
contracts/modified_contracts.md
contracts/modified_contracts.txt
contracts/commit_sha_base_ref.txt
contracts/changesets
retention-days: 7
# some of the artifacts can only be generated on product level, and we cannot scope them to single contracts
# some product-level modifications might also require shared contracts changes, so if these happened we need to generate artifacts for shared contracts as well
coverage-and-book:
if: ${{ needs.changes.outputs.product_changes == 'true' }}
name: Generate Docs and Code Coverage reports
runs-on: ubuntu-22.04
needs: [changes, gather-basic-info]
steps:
- name: Prepare exclusion list
id: prepare-exclusion-list
run: |
cat <<EOF > coverage_exclusions.json
["automation", "functions", "vrf"]
EOF
coverage_exclusions=$(cat coverage_exclusions.json | jq -c .)
echo "coverage_exclusions=$coverage_exclusions" >> $GITHUB_OUTPUT
- name: Checkout the repo
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- name: Setup NodeJS
uses: ./.github/actions/setup-nodejs
- name: Create directories
shell: bash
run: |
mkdir -p contracts/code-coverage
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0
with:
version: ${{ needs.gather-basic-info.outputs.foundry_version }}
# required for code coverage report generation
- name: Setup LCOV
uses: hrishikesh-kadam/setup-lcov@f5da1b26b0dcf5d893077a3c4f29cf78079c841d # v1.0.0
- name: Run Forge build for product contracts
if: ${{ needs.changes.outputs.product_changes == 'true' }}
run: |
forge --version
forge build
working-directory: contracts
env:
FOUNDRY_PROFILE: ${{ inputs.product }}
- name: Run coverage for product contracts
if: ${{ !contains(fromJson(steps.prepare-exclusion-list.outputs.coverage_exclusions), inputs.product) && needs.changes.outputs.product_changes == 'true' }}
working-directory: contracts
run: forge coverage --report lcov --report-file code-coverage/lcov.info
env:
FOUNDRY_PROFILE: ${{ inputs.product }}
- name: Generate Code Coverage HTML report for product contracts
if: ${{ !contains(fromJson(steps.prepare-exclusion-list.outputs.coverage_exclusions), inputs.product) && needs.changes.outputs.product_changes == 'true' }}
shell: bash
working-directory: contracts
run: genhtml code-coverage/lcov.info --branch-coverage --output-directory code-coverage
- name: Run Forge doc for product contracts
if: ${{ needs.changes.outputs.product_changes == 'true' }}
run: forge doc --build -o docs
working-directory: contracts
env:
FOUNDRY_PROFILE: ${{ inputs.product }}
- name: Upload Artifacts for product contracts
if: ${{ needs.changes.outputs.product_changes == 'true' }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
timeout-minutes: 2
continue-on-error: true
with:
name: tmp-${{ inputs.product }}-artifacts
path: |
contracts/docs
contracts/code-coverage/lcov-.info
contracts/code-coverage
retention-days: 7
# Generates UML diagrams and Slither reports for modified contracts
uml-static-analysis:
if: ${{ needs.changes.outputs.product_changes == 'true' }}
name: Generate UML and Slither reports for modified contracts
runs-on: ubuntu-22.04
needs: [changes, gather-basic-info]
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout the repo
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
fetch-depth: 0
- name: Setup NodeJS
uses: ./.github/actions/setup-nodejs
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0
with:
version: ${{ needs.gather-basic-info.outputs.foundry_version }}
- name: Install Sol2uml
run: |
npm link sol2uml --only=production
- name: Set up Python
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f #v5.1.1
with:
python-version: '3.8'
- name: Install solc-select and solc
uses: ./.github/actions/setup-solc-select
with:
to_install: '0.8.19'
to_use: '0.8.19'
- name: Install Slither
uses: ./.github/actions/setup-slither
- name: Generate UML
shell: bash
run: |
contract_list="${{ needs.changes.outputs.product_files }}"
# modify remappings so that solc can find dependencies
./contracts/scripts/ci/modify_remappings.sh contracts contracts/remappings.txt
mv remappings_modified.txt remappings.txt
./contracts/scripts/ci/generate_uml.sh "./" "contracts/uml-diagrams" "$contract_list"
- name: Generate Slither Markdown reports
run: |
contract_list="${{ needs.changes.outputs.product_files }}"
echo "::debug::Processing contracts: $contract_list"
./contracts/scripts/ci/generate_slither_report.sh "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/" contracts/configs/slither/.slither.config-artifacts.json "." "$contract_list" "contracts/slither-reports" "--solc-remaps @=contracts/node_modules/@"
- name: Upload UMLs and Slither reports
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
timeout-minutes: 10
continue-on-error: true
with:
name: tmp-contracts-artifacts
path: |
contracts/uml-diagrams
contracts/slither-reports
retention-days: 7
- name: Validate if all Slither run for all contracts
uses: ./.github/actions/validate-solidity-artifacts
with:
validate_slither_reports: 'true'
validate_uml_diagrams: 'true'
slither_reports_path: 'contracts/slither-reports'
uml_diagrams_path: 'contracts/uml-diagrams'
sol_files: ${{ needs.changes.outputs.product_files }}
gather-all-artifacts:
name: Gather all artifacts
if: ${{ needs.changes.outputs.product_changes == 'true' }}
runs-on: ubuntu-latest
needs: [coverage-and-book, uml-static-analysis, gather-basic-info, changes]
steps:
- name: Download all artifacts
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
path: review_artifacts
merge-multiple: true
- name: Upload all artifacts as single package
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: review-artifacts-${{ inputs.product }}-${{ github.sha }}
path: review_artifacts
retention-days: 60
- name: Remove temporary artifacts
uses: geekyeggo/delete-artifact@24928e75e6e6590170563b8ddae9fac674508aa1 # v5.0
with:
name: tmp-*
- name: Print Artifact URL in job summary
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACTS=$(gh api -X GET repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts)
ARTIFACT_ID=$(echo "$ARTIFACTS" | jq '.artifacts[] | select(.name=="review-artifacts-${{ inputs.product }}-${{ github.sha }}") | .id')
echo "Artifact ID: $ARTIFACT_ID"
echo "# Solidity Review Artifact Generated" >> $GITHUB_STEP_SUMMARY
echo "Base Ref used: **${{ inputs.base_ref }}**" >> $GITHUB_STEP_SUMMARY
echo "Commit SHA used: **${{ github.sha }}**" >> $GITHUB_STEP_SUMMARY
echo "[Artifact URL](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID)" >> $GITHUB_STEP_SUMMARY
notify-no-changes:
if: ${{ needs.changes.outputs.product_changes == 'false' }}
needs: [changes]
runs-on: ubuntu-latest
steps:
- name: Print warning in job summary
shell: bash
run: |
echo "# Solidity Review Artifact NOT Generated" >> $GITHUB_STEP_SUMMARY
echo "Base Ref used: **${{ inputs.base_ref }}**" >> $GITHUB_STEP_SUMMARY
echo "Commit SHA used: **${{ github.sha }}**" >> $GITHUB_STEP_SUMMARY
echo "## Reason: No modified Solidity files found for ${{ inputs.product }}" >> $GITHUB_STEP_SUMMARY
echo "* no modified Solidity files found between ${{ inputs.base_ref }} and ${{ github.sha }} commits" >> $GITHUB_STEP_SUMMARY
echo "* or they are located outside of ./contracts/src/v0.8 folder" >> $GITHUB_STEP_SUMMARY
echo "* or they were limited to test files" >> $GITHUB_STEP_SUMMARY
exit 1