feat: Implemented soroban Analysis Artifact with full synchrony #286
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: GasGuard Scan | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| jobs: | |
| gasguard-scan: | |
| name: GasGuard Security Scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 10.10.0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: | | |
| if [ -f "pnpm-lock.yaml" ]; then | |
| pnpm install --frozen-lockfile | |
| else | |
| echo "No pnpm-lock.yaml found" | |
| exit 1 | |
| fi | |
| - name: Build GasGuard | |
| run: | | |
| echo "Building GasGuard..." | |
| pnpm run build || echo "Build completed with warnings" | |
| - name: Find Solidity files | |
| id: find-files | |
| run: | | |
| files=$(find . -path "*/node_modules" -prune -o -name "*.sol" -type f -print | tr '\n' ' ') | |
| echo "files=$files" >> $GITHUB_OUTPUT | |
| echo "Found files: $files" | |
| - name: Run GasGuard Scan | |
| id: gasguard-scan | |
| run: | | |
| echo "Running GasGuard scan..." | |
| # Create scan results directory | |
| mkdir -p gasguard-results | |
| # Initialize scan variables | |
| total_issues=0 | |
| high_severity_issues=0 | |
| critical_issues=0 | |
| total_gas_savings=0 | |
| # Scan each Solidity file | |
| IFS=' ' read -ra FILES <<< "${{ steps.find-files.outputs.files }}" | |
| for file in "${FILES[@]}"; do | |
| if [ -f "$file" ]; then | |
| echo "Scanning: $file" | |
| # Run GasGuard analysis (simulated - in real implementation would use actual CLI) | |
| scan_result=$(node -e " | |
| const fs = require('fs'); | |
| const content = fs.readFileSync('$file', 'utf8'); | |
| // Simulate GasGuard rule checks | |
| const issues = []; | |
| let gasSavings = 0; | |
| // Check for unused variables (SOL-003) | |
| const lines = content.split('\n'); | |
| lines.forEach((line, index) => { | |
| if (line.includes('uint ') && line.includes(';') && !line.includes('//')) { | |
| const varMatch = line.match(/uint\s+(\w+)/); | |
| if (varMatch) { | |
| const varName = varMatch[1]; | |
| const usageCount = (content.match(new RegExp(varName, 'g')) || []).length; | |
| if (usageCount <= 1) { | |
| issues.push({ | |
| rule: 'SOL-003', | |
| severity: 'Warning', | |
| message: \`Variable '\${varName}' is declared but never used\`, | |
| line: index + 1, | |
| gasSavings: 100 | |
| }); | |
| gasSavings += 100; | |
| } | |
| } | |
| } | |
| // Check for string storage (SOL-001) | |
| if (line.includes('string ') && line.includes('public')) { | |
| issues.push({ | |
| rule: 'SOL-001', | |
| severity: 'Warning', | |
| message: 'Consider replacing string with bytes32 for short values', | |
| line: index + 1, | |
| gasSavings: 5000 | |
| }); | |
| gasSavings += 5000; | |
| } | |
| }); | |
| console.log(JSON.stringify({ | |
| file: '$file', | |
| issues: issues, | |
| gasSavings: gasSavings | |
| })); | |
| ") | |
| # Parse scan result | |
| issues_count=$(echo "$scan_result" | jq -r '.issues | length') | |
| high_count=$(echo "$scan_result" | jq -r '.issues[] | select(.severity == "Error" or .severity == "Critical") | .rule' | wc -l) | |
| critical_count=$(echo "$scan_result" | jq -r '.issues[] | select(.severity == "Critical") | .rule' | wc -l) | |
| file_gas_savings=$(echo "$scan_result" | jq -r '.gasSavings // 0') | |
| # Update totals | |
| total_issues=$((total_issues + issues_count)) | |
| high_severity_issues=$((high_severity_issues + high_count)) | |
| critical_issues=$((critical_issues + critical_count)) | |
| total_gas_savings=$((total_gas_savings + file_gas_savings)) | |
| # Save individual file results | |
| echo "$scan_result" > "gasguard-results/$(basename $file .sol).json" | |
| echo " - Issues: $issues_count, High Severity: $high_count, Critical: $critical_count, Gas Savings: $file_gas_savings" | |
| fi | |
| done | |
| # Create summary report | |
| cat > gasguard-results/summary.json << EOF | |
| { | |
| "total_files_scanned": $(echo "$FILES" | wc -w), | |
| "total_issues": $total_issues, | |
| "high_severity_issues": $high_severity_issues, | |
| "critical_issues": $critical_issues, | |
| "total_gas_savings": $total_gas_savings, | |
| "scan_timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| } | |
| EOF | |
| echo "Scan completed:" | |
| echo " - Files scanned: $(echo "$FILES" | wc -w)" | |
| echo " - Total issues: $total_issues" | |
| echo " - High severity issues: $high_severity_issues" | |
| echo " - Critical issues: $critical_issues" | |
| echo " - Total potential gas savings: $total_gas_savings" | |
| # Set outputs for GitHub Actions | |
| echo "total-issues=$total_issues" >> $GITHUB_OUTPUT | |
| echo "high-severity=$high_severity_issues" >> $GITHUB_OUTPUT | |
| echo "critical=$critical_issues" >> $GITHUB_OUTPUT | |
| echo "gas-savings=$total_gas_savings" >> $GITHUB_OUTPUT | |
| - name: Generate Scan Report | |
| run: | | |
| echo "# GasGuard Scan Report" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Files Scanned**: $(jq -r '.total_files_scanned' gasguard-results/summary.json)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Total Issues**: ${{ steps.gasguard-scan.outputs.total-issues }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **High Severity Issues**: ${{ steps.gasguard-scan.outputs.high-severity }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Critical Issues**: ${{ steps.gasguard-scan.outputs.critical }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Potential Gas Savings**: ${{ steps.gasguard-scan.outputs.gas-savings }} gas" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Add detailed findings | |
| echo "## Detailed Findings" >> $GITHUB_STEP_SUMMARY | |
| for result_file in gasguard-results/*.json; do | |
| if [ "$result_file" != "gasguard-results/summary.json" ]; then | |
| filename=$(basename "$result_file" .json) | |
| issues_count=$(jq -r '.issues | length' "$result_file") | |
| if [ "$issues_count" -gt 0 ]; then | |
| echo "### $filename.sol ($issues_count issues)" >> $GITHUB_STEP_SUMMARY | |
| jq -r '.issues[] | "- Line \(.line): \(.message) (\(.rule))"' "$result_file" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| fi | |
| done | |
| - name: Check for High Severity Issues | |
| id: severity-check | |
| run: | | |
| if [ "${{ steps.gasguard-scan.outputs.critical }}" -gt 0 ]; then | |
| echo "status=error" >> $GITHUB_OUTPUT | |
| echo "message=Critical security issues detected" >> $GITHUB_OUTPUT | |
| elif [ "${{ steps.gasguard-scan.outputs.high-severity }}" -gt 5 ]; then | |
| echo "status=error" >> $GITHUB_OUTPUT | |
| echo "message=Too many high severity issues detected" >> $GITHUB_OUTPUT | |
| elif [ "${{ steps.gasguard-scan.outputs.high-severity }}" -gt 0 ]; then | |
| echo "status=warning" >> $GITHUB_OUTPUT | |
| echo "message=High severity issues detected" >> $GITHUB_OUTPUT | |
| else | |
| echo "status=success" >> $GITHUB_OUTPUT | |
| echo "message=No critical issues detected" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create Status Badge | |
| run: | | |
| status="${{ steps.severity-check.outputs.status }}" | |
| message="${{ steps.severity-check.outputs.message }}" | |
| case $status in | |
| "error") | |
| color="red" | |
| ;; | |
| "warning") | |
| color="yellow" | |
| ;; | |
| *) | |
| color="green" | |
| ;; | |
| esac | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload Scan Results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: gasguard-scan-results | |
| path: gasguard-results/ | |
| retention-days: 30 | |
| - name: Fail on Critical Issues | |
| if: steps.severity-check.outputs.status == 'error' | |
| run: | | |
| echo "::error::${{ steps.severity-check.outputs.message }}" | |
| echo "::error::Please fix the critical issues before merging." | |
| exit 1 | |
| - name: Warn on High Severity Issues | |
| if: steps.severity-check.outputs.status == 'warning' | |
| run: | | |
| echo "::warning::${{ steps.severity-check.outputs.message }}" | |
| echo "::warning::Consider addressing high severity issues before merging." | |
| - name: Success Message | |
| if: steps.severity-check.outputs.status == 'success' | |
| run: | | |
| echo "✅ GasGuard scan passed successfully!" | |
| echo "🎉 Potential gas savings: ${{ steps.gasguard-scan.outputs.gas-savings }} gas" | |
| gasguard-comment: | |
| name: GasGuard PR Comment | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| needs: gasguard-scan | |
| continue-on-error: true | |
| steps: | |
| - name: Download Scan Results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: gasguard-scan-results | |
| path: scan-results/ | |
| - name: Comment on PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| try { | |
| const summary = JSON.parse(fs.readFileSync('scan-results/summary.json', 'utf8')); | |
| let status = '✅ PASSED'; | |
| let statusMessage = '🎉 **No critical issues found!**'; | |
| if (summary.critical_issues > 0) { | |
| status = '❌ FAILED'; | |
| statusMessage = '🚨 **Critical issues detected! Please fix before merging.**'; | |
| } else if (summary.high_severity_issues > 0) { | |
| status = '⚠️ WARNING'; | |
| statusMessage = '⚠️ **High severity issues detected. Consider addressing before merging.**'; | |
| } | |
| const comment = "## 🔍 GasGuard Scan Results\n\n" + | |
| "**Scan Summary:**\n" + | |
| "- 📁 Files scanned: " + summary.total_files_scanned + "\n" + | |
| "- ⚠️ Total issues: " + summary.total_issues + "\n" + | |
| "- 🚨 High severity issues: " + summary.high_severity_issues + "\n" + | |
| "- 💀 Critical issues: " + summary.critical_issues + "\n" + | |
| "- ⛽ Potential gas savings: " + summary.total_gas_savings + " gas\n\n" + | |
| "**Status: " + status + "**\n\n" + | |
| statusMessage + "\n\n" + | |
| "*Scan completed on " + new Date(summary.scan_timestamp).toLocaleString() + "*"; | |
| try { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| } catch (commentError) { | |
| console.log('Could not post PR comment (may lack write permissions on fork PRs):', commentError.message); | |
| } | |
| } catch (error) { | |
| console.log('Could not read scan results:', error.message); | |
| } |