diff --git a/.github/workflows/ensureSCCoreDevApproval.yml b/.github/workflows/ensureSCCoreDevApproval.yml index 3651382e..ce95e814 100644 --- a/.github/workflows/ensureSCCoreDevApproval.yml +++ b/.github/workflows/ensureSCCoreDevApproval.yml @@ -17,6 +17,7 @@ jobs: env: GH_PAT: ${{ secrets.GIT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONTINUE: false # makes sure that variable is correctly initialized in all cases run: | ##### unset the default git token (does not have sufficient rights to get team members) diff --git a/.github/workflows/protectAuditLabels.yml b/.github/workflows/protectAuditLabels.yml index de2a3935..d7e78d3f 100644 --- a/.github/workflows/protectAuditLabels.yml +++ b/.github/workflows/protectAuditLabels.yml @@ -15,6 +15,8 @@ on: jobs: protect_audit_labels: runs-on: ubuntu-latest + env: + CONTINUE: false # makes sure that variable is correctly initialized in all cases steps: - name: Checkout repository @@ -46,7 +48,6 @@ jobs: echo "CONTINUE=false" >> $GITHUB_ENV exit 0 fi - echo "CONTINUE=true" >> $GITHUB_ENV echo "This action was triggered by: ${{ github.actor }}" - name: Protect Audit Labels diff --git a/.github/workflows/protectSecurityRelevantCode.yml b/.github/workflows/protectSecurityRelevantCode.yml index 39046bda..e35d331d 100644 --- a/.github/workflows/protectSecurityRelevantCode.yml +++ b/.github/workflows/protectSecurityRelevantCode.yml @@ -13,6 +13,8 @@ jobs: protect-critical-code: if: ${{ github.event.pull_request.draft == false }} runs-on: ubuntu-latest + env: + CONTINUE: false # makes sure that variable is correctly initialized in all cases permissions: pull-requests: write steps: diff --git a/.github/workflows/verifyAudit.yml b/.github/workflows/verifyAudit.yml index d2746338..96afc40e 100644 --- a/.github/workflows/verifyAudit.yml +++ b/.github/workflows/verifyAudit.yml @@ -25,6 +25,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GIT_ACTIONS_BOT_PAT_CLASSIC }} AUDIT_LOG_PATH: 'audit/auditLog.json' PR_NUMBER: ${{ github.event.pull_request.number }} + CONTINUE: false # makes sure that variable is correctly initialized in all cases permissions: pull-requests: write @@ -45,7 +46,8 @@ jobs: ##### Make sure that there are modified files if [[ -z $FILES ]]; then echo -e "\033[31mNo files found. This should not happen. Please check the code of the Github action. Aborting now.\033[0m" - echo "CONTINUE=false" >> "$GITHUB_ENV" + echo "CONTINUE=false" >> "$GITHUB_ENV" # explicit assignment, even though already initialized with false + exit 1 fi ##### Initialize empty variables @@ -67,7 +69,6 @@ jobs: echo -e "\033[31mProtected contracts found in this PR.\033[0m" echo "PROTECTED_CONTRACTS: $PROTECTED_CONTRACTS" echo "AUDIT_REQUIRED=true" >> "$GITHUB_ENV" - echo "$AUDIT_REQUIRED" > audit_required.txt echo -e "$PROTECTED_CONTRACTS" > protected_contracts.txt fi @@ -75,6 +76,7 @@ jobs: uses: actions/github-script@v7 env: AUDIT_REQUIRED: ${{ env.AUDIT_REQUIRED }} + CONTINUE: ${{ env.CONTINUE }} with: script: | const { execSync } = require('child_process'); @@ -126,6 +128,12 @@ jobs: console.log(`${colors.green}Opposite label "${oppositeLabel}" is not assigned. No action needed.${colors.reset}`); } + // Remove the AuditCompleted label if it is present + if (assignedLabels.includes('AuditCompleted')) { + console.log(`Now removing AuditCompleted label`); + execSync(`gh pr edit ${{ github.event.pull_request.number }} --remove-label "AuditCompleted"`, { stdio: 'inherit' }); + } + // fetch all currently assigned labels again assignedLabels = [] try { @@ -154,17 +162,23 @@ jobs: core.exportVariable('AUDIT_COMPLETED_ASSIGNED', 'true'); } else - console.log(`Label 'AuditCompleted' is currently not assigned`); + console.log(`${colors.green}Label 'AuditCompleted' is currently not assigned${colors.reset}`); + + // set CONTINUE to true to make sure the following steps are executed + core.exportVariable('CONTINUE', 'true'); - name: Check Audit Log continue-on-error: true id: check-audit-log - if: env.AUDIT_REQUIRED == 'true' + if: ${{ always() && env.AUDIT_REQUIRED == 'true' && env.CONTINUE == 'true' }} # always() ensures that validation is always executed, even if env variable is not set run: | echo "This step will make sure that an audit is logged for each contract modified/added by this PR." echo "It will also make sure that no information is missing in the audit log and that the information is meaningful." + # set CONTINUE to false to ensure that action fails (correctly) if an error occurs unexpectedly + echo "CONTINUE=false" >> "$GITHUB_ENV" + # load list of protected contracts PROTECTED_CONTRACTS=$(cat protected_contracts.txt) @@ -381,7 +395,7 @@ jobs: echo "Assigning label 'AuditCompleted' next" - name: Assign label "AuditCompleted" if all checks passed - if: ${{ env.AUDIT_REQUIRED == 'true' && env.CONTINUE == 'true' }} + if: ${{ always() && env.AUDIT_REQUIRED == 'true' && env.CONTINUE == 'true' }} uses: actions-ecosystem/action-add-labels@v1 id: assign_label with: @@ -392,14 +406,14 @@ jobs: - name: Remove label "AuditCompleted" in case check was not successful but label was assigned in earlier checks continue-on-error: true # This ensures the step will execute even if the job has a failed status. uses: actions-ecosystem/action-remove-labels@v1 - if: ${{ env.AUDIT_COMPLETED_ASSIGNED && (env.CONTINUE == 'false' || (env.CONTINUE == 'true' && env.AUDIT_REQUIRED == 'false'))}} + if: ${{ always() && env.AUDIT_COMPLETED_ASSIGNED && (env.CONTINUE == 'false' || (env.CONTINUE == 'true' && env.AUDIT_REQUIRED == 'false'))}} with: github_token: ${{ secrets.GIT_ACTIONS_BOT_PAT_CLASSIC }} # we use the token of the lifi-action-bot so the label protection check will pass labels: 'AuditCompleted' number: ${{ env.PR_NUMBER }} - name: Fail the git action if any critical step failed - if: env.CONTINUE == 'false' # This step runs only if a failure was recorded + if: always() && env.CONTINUE == 'false' # This step runs only if a failure was recorded run: | echo -e "\033[31mError: One or more critical steps failed. Failing the job.\033[0m" diff --git a/.github/workflows/versionCheck.yml b/.github/workflows/versionCheck.yml index a7c42f3f..7245cfac 100644 --- a/.github/workflows/versionCheck.yml +++ b/.github/workflows/versionCheck.yml @@ -18,6 +18,8 @@ jobs: # will only run once the PR is in "Ready for Review" state if: ${{ github.event.pull_request.draft == false }} runs-on: ubuntu-latest + env: + CONTINUE: false # makes sure that variable is correctly initialized in all cases steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows_deactivated/enforceTestCoverage.yml b/.github/workflows_deactivated/enforceTestCoverage.yml deleted file mode 100644 index 1fff0d78..00000000 --- a/.github/workflows_deactivated/enforceTestCoverage.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Enforce Min Test Coverage - -# - will make sure that (Foundry) unit test coverage is above min threshold -# - we start with 75%, planning to increase to 100% until EOY 2024 -# - Only the 'lines' coverage counts as 'branch' coverage is not reliable - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - enforce-min-test-coverage: - runs-on: ubuntu-latest - # will only run once the PR is in "Ready for Review" state - if: ${{ github.event.pull_request.draft == false }} - - permissions: - pull-requests: write - contents: read - env: - ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} - ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} - ETH_NODE_URI_GOERLI: ${{ secrets.ETH_NODE_URI_GOERLI }} - ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} - ETH_NODE_URI_BSC: ${{ secrets.ETH_NODE_URI_BSC }} - ETH_NODE_URI_GNOSIS: ${{ secrets.ETH_NODE_URI_GNOSIS }} - GIT_TOKEN: ${{ secrets.GIT_TOKEN }} - MIN_TEST_COVERAGE: 75 # 75 percent for now, will be increased to 100% gradually until the end of 2024 - steps: - - uses: actions/checkout@v4.1.7 - - - name: Set up Node.js - uses: actions/setup-node@v4.1.0 - with: - node-version: '20' - - - name: Install dev dependencies - run: yarn install - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1.2.0 - with: - version: nightly - - - name: Install Dependencies - run: forge install - - - name: Install Git Submodules - run: | - git config --global url."https://github.com/".insteadOf "git@github.com:" - git submodule update --init --recursive - - - name: Generate Coverage Report - run: | - forge coverage --report lcov --force - echo "Filtering coverage report to only contain coverage info about src/ folder now" - npx ts-node script/utils/filter_lcov.ts lcov.info lcov-filtered.info 'test/' 'script/' - echo "Coverage report filtered" - - - name: Generate Coverage Summary - run: | - # Path to the lcov info file - LCOV_FILE="lcov-filtered.info" - - # Initialize counters - TOTAL_LINES_FOUND=0 - TOTAL_LINES_HIT=0 - TOTAL_FUNCTIONS_FOUND=0 - TOTAL_FUNCTIONS_HIT=0 - TOTAL_BRANCHES_FOUND=0 - TOTAL_BRANCHES_HIT=0 - - # Read through the lcov file - while IFS= read -r line; do - case $line in - LF:*) - TOTAL_LINES_FOUND=$((TOTAL_LINES_FOUND + ${line#LF:})) - ;; - LH:*) - TOTAL_LINES_HIT=$((TOTAL_LINES_HIT + ${line#LH:})) - ;; - FNF:*) - TOTAL_FUNCTIONS_FOUND=$((TOTAL_FUNCTIONS_FOUND + ${line#FNF:})) - ;; - FNH:*) - TOTAL_FUNCTIONS_HIT=$((TOTAL_FUNCTIONS_HIT + ${line#FNH:})) - ;; - BRF:*) - TOTAL_BRANCHES_FOUND=$((TOTAL_BRANCHES_FOUND + ${line#BRF:})) - ;; - BRH:*) - TOTAL_BRANCHES_HIT=$((TOTAL_BRANCHES_HIT + ${line#BRH:})) - ;; - esac - done < "$LCOV_FILE" - - # Calculate percentages with high precision - LINE_COVERAGE_PERCENTAGE=$(echo "scale=4; $TOTAL_LINES_HIT / $TOTAL_LINES_FOUND * 100" | bc) - FUNCTION_COVERAGE_PERCENTAGE=$(echo "scale=4; $TOTAL_FUNCTIONS_HIT / $TOTAL_FUNCTIONS_FOUND * 100" | bc) - BRANCH_COVERAGE_PERCENTAGE=$(echo "scale=4; $TOTAL_BRANCHES_HIT / $TOTAL_BRANCHES_FOUND * 100" | bc) - - # Format results with two decimal places and alignment - LINE_COVERAGE_PERCENTAGE=$(printf "%.2f" "$LINE_COVERAGE_PERCENTAGE") - FUNCTION_COVERAGE_PERCENTAGE=$(printf "%.2f" "$FUNCTION_COVERAGE_PERCENTAGE") - BRANCH_COVERAGE_PERCENTAGE=$(printf "%.2f" "$BRANCH_COVERAGE_PERCENTAGE") - - # Prepare aligned output - LINE_COVERAGE_REPORT=$(printf "Line Coverage: %6s%% (%4d / %4d lines)" "$LINE_COVERAGE_PERCENTAGE" "$TOTAL_LINES_HIT" "$TOTAL_LINES_FOUND") - FUNCTION_COVERAGE_REPORT=$(printf "Function Coverage: %6s%% (%4d / %4d functions)" "$FUNCTION_COVERAGE_PERCENTAGE" "$TOTAL_FUNCTIONS_HIT" "$TOTAL_FUNCTIONS_FOUND") - BRANCH_COVERAGE_REPORT=$(printf "Branch Coverage: %6s%% (%4d / %4d branches)" "$BRANCH_COVERAGE_PERCENTAGE" "$TOTAL_BRANCHES_HIT" "$TOTAL_BRANCHES_FOUND") - - # Check against minimum threshold - if (( $(echo "$LINE_COVERAGE_PERCENTAGE >= $MIN_TEST_COVERAGE" | bc -l) )); then - RESULT_COVERAGE_REPORT="Test coverage ($LINE_COVERAGE_PERCENTAGE%) is above min threshold ($MIN_TEST_COVERAGE%). Check passed." - else - RESULT_COVERAGE_REPORT="Test coverage ($LINE_COVERAGE_PERCENTAGE%) is below min threshold ($MIN_TEST_COVERAGE%). Check failed." - echo $RESULT_COVERAGE_REPORT - exit 1 - fi - - # Output result_COVERAGE_REPORTs - echo "$LINE_COVERAGE_REPORT" - echo "$FUNCTION_COVERAGE_REPORT" - echo "$BRANCH_COVERAGE_REPORT" - echo "$RESULT_COVERAGE_REPORT" - - # Store in GitHub environment variables - echo "LINE_COVERAGE_REPORT=$LINE_COVERAGE_REPORT" >> $GITHUB_ENV - echo "FUNCTION_COVERAGE_REPORT=$FUNCTION_COVERAGE_REPORT" >> $GITHUB_ENV - echo "BRANCH_COVERAGE_REPORT=$BRANCH_COVERAGE_REPORT" >> $GITHUB_ENV - echo "RESULT_COVERAGE_REPORT=$RESULT_COVERAGE_REPORT" >> $GITHUB_ENV - - - name: Comment with Coverage Summary in PR - uses: mshick/add-pr-comment@v2 - with: - message: | - ## Test Coverage Report - ${{ env.LINE_COVERAGE_REPORT }} - ${{ env.FUNCTION_COVERAGE_REPORT }} - ${{ env.BRANCH_COVERAGE_REPORT }} - ${{ env.RESULT_COVERAGE_REPORT }} diff --git a/.github/workflows_deactivated/ensureDeployProcessIntegrity.yml b/.github/workflows_deactivated/ensureDeployProcessIntegrity.yml index 6ae3bb93..5e2d1837 100644 --- a/.github/workflows_deactivated/ensureDeployProcessIntegrity.yml +++ b/.github/workflows_deactivated/ensureDeployProcessIntegrity.yml @@ -11,6 +11,8 @@ on: jobs: protect-security-system: runs-on: ubuntu-latest + env: + CONTINUE: false # makes sure that variable is correctly initialized in all cases permissions: pull-requests: write steps: diff --git a/.github/workflows_deactivated/ensureSCCoreDevApproval.yml b/.github/workflows_deactivated/ensureSCCoreDevApproval.yml deleted file mode 100644 index db633e50..00000000 --- a/.github/workflows_deactivated/ensureSCCoreDevApproval.yml +++ /dev/null @@ -1,121 +0,0 @@ -# - Smart Contract Core Dev Approval checker -# - makes sure that every pull_request is at least reviewed by one Smart Contract Core Dev -# (member of group https://github.com/orgs/lifinance/teams/smart-contract-core) - -name: SC Core Dev Approval Check - -on: - pull_request: - types: [opened, synchronize, reopened] - pull_request_review: - types: [submitted] - -jobs: - core-dev-approval: - if: ${{ github.event.pull_request.draft == false }} # will only run once the PR is in "Ready for Review" state - runs-on: ubuntu-latest - steps: - - name: Get smart-contract-core Team Members - env: - GH_PAT: ${{ secrets.GIT_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - - ##### unset the default git token (does not have sufficient rights to get team members) - unset GITHUB_TOKEN - ##### use the Personal Access Token to log into git CLI - echo $GH_PAT | gh auth login --with-token - - ##### Function that uses github's REST API via CLI to get team members - getTeamMembers() { - local org=$1 - local team=$2 - gh api \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/orgs/$org/teams/$team/members" | jq -r '.[].login' - } - - ORG_NAME="lifinance" - TEAM_SLUG="smart-contract-core" - - # Get members of each group - echo "Fetching members of $TEAM_SLUG..." - MEMBERS=$(getTeamMembers $ORG_NAME $TEAM_SLUG) - - #### check if any members were returned - if [[ -z $MEMBERS ]]; then - echo -e "\033[31mERROR: Could not retrieve team members of group $TEAM_SLUG\033[0m" - echo "CONTINUE=false" >> "$GITHUB_ENV" - exit 1 - fi - - echo "The following Github users are members of team smart-contract-core: " - echo "$MEMBERS" - - echo -e "$MEMBERS" > sc_core_dev_members.txt - echo "CONTINUE=true" >> "$GITHUB_ENV" - - - name: Check if PR is approved by at least one SC core dev - id: check-core-dev-approval - if: env.CONTINUE == 'true' - uses: actions/github-script@v7 - env: - PR_NUMBER: ${{ github.event.number }} - with: - script: | - const fs = require('fs'); - // ANSI escape codes for colors (used for colored output in Git action console) - const colors = { - reset: "\033[0m", - red: "\033[31m", - green: "\033[32m", - }; - - const coreDevsFile = 'sc_core_dev_members.txt'; - // Read handles from file - const coreDevs = fs.readFileSync(coreDevsFile, 'utf-8').split(/\r?\n/).filter(Boolean); - - // get all reviewers that have approved this PR - const { data: reviews } = await github.rest.pulls.listReviews({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: process.env.PR_NUMBER, - }); - - // make sure that reviews are available - if(!reviews || reviews.length === 0) { - console.log(`${colors.red}Could not get reviewers of this PR from Github. Are there any reviews yet?${colors.reset}`); - console.log(`${colors.red}Check failed.${colors.reset}`); - core.setFailed("Required approval is missing"); - return - } - - // Filter to only include reviews that have "APPROVED" status - const approvedReviews = reviews.filter(review => review.state === 'APPROVED'); - - if(!approvedReviews.length === 0) { - console.log(`${colors.red}Could not find any reviews with approval.${colors.reset}`); - console.log(`${colors.red}Cannot continue. Check failed.${colors.reset}`); - core.setFailed("Required approval is missing"); - return - } - - // extract the git login handles of all reviewers that approved this PR - const reviewerHandles = approvedReviews.map(review => review.user.login); - - if(approvedReviews.length === 0) - console.log(`${colors.red}This PR has no approvals${colors.reset}`); - else - console.log(`This PR has been approved by the following git members: ${reviewerHandles}`); - - // check if at least one of these reviewers is member in smart-contract-core group - if (reviewerHandles.some((handle) => coreDevs.includes(handle))) { - console.log(`${colors.green}The current PR is approved by a member of the smart-contract-core group.${colors.reset}`); - console.log(`${colors.green}Check passed.${colors.reset}`); - core.setOutput('approved', 'true'); - } else { - console.log(`${colors.red}The PR requires a missing approval by a member of the smart-contract-core group (https://github.com/orgs/lifinance/teams/smart-contract-core).${colors.reset}`); - console.log(`${colors.red}Check failed.${colors.reset}`); - core.setFailed("Required approval is missing"); - } diff --git a/.github/workflows_deactivated/protectAuditCompletedLabel.yml b/.github/workflows_deactivated/protectAuditCompletedLabel.yml deleted file mode 100644 index 8a2b8e47..00000000 --- a/.github/workflows_deactivated/protectAuditCompletedLabel.yml +++ /dev/null @@ -1,78 +0,0 @@ -# - Protect "AuditCompleted" Label -# - makes sure that the label "AuditCompleted" can only be assigned by a Github action and not by a human actor -# - will undo any unauthorized change of this label -# - will fail if it runs into an error, otherwise pass - -name: Protect "AuditCompleted" Label - -on: - pull_request_target: #### << needs to be changed to 'pull_request' to activate it - types: [labeled, unlabeled] - -jobs: - protect_audit_label: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Log Event Payload - run: | - if [[ "${{ github.actor }}" == "github-actions" ]]; then - echo "This action was triggered by another GitHub Action." - else - echo "This action was triggered by a user: ${{ github.actor }}." - fi - echo "${{ github.event }}" - - - name: Check if "AuditCompleted" label was modified - env: - GITHUB_TOKEN: ${{ secrets.LIFI_GIT_ACTIONS_TOKEN }} - GH_PAT: ${{ secrets.LIFI_GIT_ACTIONS_TOKEN }} - run: | - # The label being monitored - TARGET_LABEL="AuditCompleted" - - # Check if the event was triggered by any other github action - if [[ "${{ github.actor }}" != "lifiGitActions" ]]; then #### TODO: REPLACE WITH GITHUB_ACTIONS_PAT and USERNAME <<<<<----------- - echo "This event was triggered by ${{ github.actor }}. Checking label..." - - # Determine if the label was added or removed - ACTION_TYPE="none" - if [[ "${{ github.event.action }}" == "labeled" && "${{ github.event.label.name }}" == "$TARGET_LABEL" ]]; then - ACTION_TYPE="added" - elif [[ "${{ github.event.action }}" == "unlabeled" && "${{ github.event.label.name }}" == "$TARGET_LABEL" ]]; then - ACTION_TYPE="removed" - fi - - # Revert the label change if necessary - if [[ "$ACTION_TYPE" != "none" ]]; then - echo -e "\033[31mUnauthorized modification of '$TARGET_LABEL' by ${{ github.actor }}. Reverting change...\033[0m" - - ##### remove or re-add label, depending on the case - if [[ "$ACTION_TYPE" == "added" ]]; then - # Remove the unauthorized label addition - gh pr edit ${{ github.event.pull_request.number }} --remove-label "$TARGET_LABEL" - elif [[ "$ACTION_TYPE" == "removed" ]]; then - # Re-add the unauthorized label removal - gh pr edit ${{ github.event.pull_request.number }} --add-label "$TARGET_LABEL" - fi - - # make sure that the label change was undone - CURRENT_LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name') - if [[ "$ACTION_TYPE" == "added" && "$CURRENT_LABELS" == *"$TARGET_LABEL"* ]]; then - echo -e "\033[31Failed to remove the unauthorized 'AuditCompleted' label.\033[0m" - exit 1 - elif [[ "$ACTION_TYPE" == "removed" && "$CURRENT_LABELS" != *"$TARGET_LABEL"* ]]; then - echo -e "\033[31Failed to re-add the 'AuditCompleted' label.\033[0m" - exit 1 - fi - - echo -e "\033[32Unauthorized label modification was successfully prevented and undone.\033[0m" - else - echo -e "\033[32mNo unauthorized modifications detected.\033[0m" - fi - else - echo -e "\033[32mLabel change initiated by GitHub Action. No checks required.\033[0m" - fi diff --git a/.github/workflows_deactivated/protectAuditFolder.yml b/.github/workflows_deactivated/protectAuditFolder.yml index d97390a0..c5860db6 100644 --- a/.github/workflows_deactivated/protectAuditFolder.yml +++ b/.github/workflows_deactivated/protectAuditFolder.yml @@ -15,6 +15,8 @@ on: jobs: protect-audit-folder: runs-on: ubuntu-latest + env: + CONTINUE: false # makes sure that variable is correctly initialized in all cases steps: - name: Checkout code