diff --git a/.github/workflows/verifyAudit.yml b/.github/workflows/verifyAudit.yml index 84c05492..38aadf56 100644 --- a/.github/workflows/verifyAudit.yml +++ b/.github/workflows/verifyAudit.yml @@ -16,6 +16,122 @@ on: types: [opened, synchronize, reopened] jobs: + check-if-audit-required: + # will only run once the PR is in "Ready for Review" state + if: ${{ github.event.pull_request.draft == false }} + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GIT_ACTIONS_BOT_PAT_CLASSIC }} + permissions: + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 ##### Fetch all history for all branches + + - name: Check PR for changes for protected folders ('src/*') + id: check_if_audit_is_required + run: | + + + ##### Get all files modified by this PR + FILES=$(git diff --name-only origin/main HEAD) + + ##### 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" + fi + + ##### Initialize empty variables + PROTECTED_CONTRACTS="" + + ##### Go through all modified file names/paths and identify contracts with path 'src/*' + while IFS= read -r FILE; do + if echo "$FILE" | grep -E '^src/.*\.sol$'; then + ##### Contract found + PROTECTED_CONTRACTS="${PROTECTED_CONTRACTS}${FILE}"$'\n' + fi + done <<< "$FILES" + + ##### Determine if audit is required + if [[ -z "$PROTECTED_CONTRACTS" ]]; then + echo -e "\033[32mNo protected contracts found in this PR.\033[0m" + echo "AUDIT_REQUIRED=false" >> "$GITHUB_ENV" + else + echo -e "\033[31mProtected contracts found in this PR.\033[0m" + echo "PROTECTED_CONTRACTS: $PROTECTED_CONTRACTS" + echo "AUDIT_REQUIRED=true" >> "$GITHUB_ENV" + echo -e "$PROTECTED_CONTRACTS" > protected_contracts.txt + fi + + - name: Assign, update, and verify labels based on check outcome + uses: actions/github-script@v7 + env: + AUDIT_REQUIRED: ${{ env.AUDIT_REQUIRED }} + with: + script: | + const { execSync } = require('child_process'); + // ANSI escape codes for colors (used for colored output in Git action console) + const colors = { + reset: "\033[0m", + red: "\033[31m", + green: "\033[32m", + }; + + // Fetch currently assigned labels from GitHub using GitHub CLI + let assignedLabels = []; + try { + // Fetch the labels directly from the pull request + const labelsOutput = execSync(`gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name'`).toString(); + + // Split the labels output into an array and trim each label + assignedLabels = labelsOutput.split('\n').map(label => label.trim()).filter(Boolean); + } catch (error) { + console.error(`${colors.red}Error fetching assigned labels: ${error.message}${colors.reset}`); + process.exit(1); + } + + // check if audit is required (determined by previous step) + const auditRequired = process.env.AUDIT_REQUIRED === 'true'; + + // determine which label should be assigned and which should be removed + const labelToAssign = auditRequired ? 'AuditRequired' : 'AuditNotRequired'; + const oppositeLabel = auditRequired ? 'AuditNotRequired' : 'AuditRequired'; + + console.log(`Currently assigned labels: ${JSON.stringify(assignedLabels)}`); + console.log(`Label '${labelToAssign}' has to be assigned to this PR`); + console.log(`Label '${oppositeLabel}' will be removed, if currently present`); + + // Assign the required label if not already present + if (!assignedLabels.includes(labelToAssign)) { + console.log(`Assigning label: ${labelToAssign}`); + execSync(`gh pr edit ${{ github.event.pull_request.number }} --add-label "${labelToAssign}"`, { stdio: 'inherit' }); + } else { + console.log(`${colors.green}Label "${labelToAssign}" is already assigned. No action needed.${colors.reset}`); + } + + // Remove the opposite label if it is present + if (assignedLabels.includes(oppositeLabel)) { + console.log(`Removing opposite label: ${oppositeLabel}`); + execSync(`gh pr edit ${{ github.event.pull_request.number }} --remove-label "${oppositeLabel}"`, { stdio: 'inherit' }); + } else { + console.log(`${colors.green}Opposite label "${oppositeLabel}" is not assigned. No action needed.${colors.reset}`); + } + + // Verify that exactly one of the two labels is assigned + const requiredLabelCount = assignedLabels.filter(label => label === 'AuditRequired').length; + const notRequiredLabelCount = assignedLabels.filter(label => label === 'AuditNotRequired').length; + const totalLabelsAssigned = requiredLabelCount + notRequiredLabelCount; + + if (totalLabelsAssigned !== 1) { + console.error(`${colors.red}Error: Exactly one of the two protected labels should be assigned but found ${totalLabelsAssigned} assigned labels.${colors.reset}`); + process.exit(1); + } else { + console.log(`${colors.green}Verified that exactly one label is assigned. Check passed :)${colors.reset}`); + } + verify-audit: # will only run once the PR is in "Ready for Review" state if: ${{ github.event.pull_request.draft == false }} @@ -343,10 +459,10 @@ jobs: done <<< "$PROTECTED_CONTRACTS" - name: Assign label "AuditCompleted" if all checks passed - if: ${{ env.CONTINUE == 'true' }} + if: ${{ env.AUDIT_REQUIRED == 'true' && env.CONTINUE == 'true' }} uses: actions-ecosystem/action-add-labels@v1 id: assign_label with: - github_token: ${{ secrets.GIT_ACTIONS_BOT_PAT_CLASSIC }} # we use the token of the git action user so the label protection check will pass + 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 }}