forge-guard 0.1.3

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! CI/CD integration — generates pipeline configurations for various platforms.

use crate::core::ForgeGuardError;

/// Generator for CI/CD pipeline configurations.
pub struct CiGenerator {
    platform: String,
}

impl CiGenerator {
    /// Create a new CI generator for the given platform.
    pub fn new(platform: &str) -> Self {
        Self {
            platform: platform.to_lowercase(),
        }
    }

    /// Get the output filename for this CI platform.
    pub fn filename(&self) -> &'static str {
        match self.platform.as_str() {
            "github" => "audit.yml",
            "gitlab" => ".gitlab-ci.yml",
            "bitbucket" => "bitbucket-pipelines.yml",
            "azure" => "azure-pipelines.yml",
            _ => "ci-config.yml",
        }
    }

    /// Generate the CI configuration content.
    pub fn generate(&self, include_deploy: bool) -> Result<String, ForgeGuardError> {
        match self.platform.as_str() {
            "github" => Ok(self.generate_github(include_deploy)),
            "gitlab" => Ok(self.generate_gitlab(include_deploy)),
            "bitbucket" => Ok(self.generate_bitbucket(include_deploy)),
            "azure" => Ok(self.generate_azure(include_deploy)),
            _ => Err(ForgeGuardError::Config(format!(
                "Unsupported CI platform: {}. Supported: github, gitlab, bitbucket, azure",
                self.platform
            ))),
        }
    }

    fn generate_github(&self, include_deploy: bool) -> String {
        let mut yaml = String::from(
            r#"name: Forge Guard Security Check

on:
  push:
    branches: [ main, master, develop ]
  pull_request:
    branches: [ main, master ]

env:
  FOUNDRY_PROFILE: ci

jobs:
  security-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

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

      - name: Install Forge Guard
        run: |
          cargo install forge-guard
          forge audit --version

      - name: Run Security Audit
        run: forge audit --strict

      - name: Run Fuzzing Campaign
        run: forge fuzz --runs 10000

      - name: Run Invariant Tests
        run: forge invariant --runs 1000

      - name: Check Dependencies
        run: forge scan --depth 1

      - name: Generate Report
        run: forge audit --report --markdown
"#,
        );

        if include_deploy {
            yaml.push_str(
                r#"
  deploy:
    runs-on: ubuntu-latest
    needs: [security-audit]
    if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

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

      - name: Final Security Check
        run: forge audit --strict --production

      - name: Safe Deploy
        run: forge deploy-safe
        env:
          ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
          PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
"#,
            );
        }

        yaml
    }

    fn generate_gitlab(&self, include_deploy: bool) -> String {
        let mut yaml = String::from(
            r#"stages:
  - security-audit
  - fuzzing
  - deploy

variables:
  FOUNDRY_PROFILE: ci

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - target/

forge-guard:
  stage: security-audit
  image: ghcr.io/foundry-rs/foundry:latest
  before_script:
    - cargo install forge-guard || true
  script:
    - forge audit --strict
    - forge scan --depth 1
  artifacts:
    paths:
      - reports/
    when: always

fuzzing:
  stage: fuzzing
  image: ghcr.io/foundry-rs/foundry:latest
  script:
    - forge fuzz --runs 10000
    - forge invariant --runs 1000
"#,
        );

        if include_deploy {
            yaml.push_str(
                r#"
deploy:
  stage: deploy
  image: ghcr.io/foundry-rs/foundry:latest
  script:
    - forge audit --strict --production
    - forge deploy-safe
  only:
    - main
  environment: production
"#,
            );
        }

        yaml
    }

    fn generate_bitbucket(&self, _include_deploy: bool) -> String {
        String::from(
            r#"image: ghcr.io/foundry-rs/foundry:latest

pipelines:
  default:
    - step:
        name: Security Audit
        script:
          - cargo install forge-guard || true
          - forge audit --strict
          - forge scan --depth 1
          - forge fuzz --runs 10000
        artifacts:
          - reports/**

  branches:
    main:
      - step:
          name: Production Security Check
          script:
            - forge audit --strict --production
            - forge deploy-safe
          deployment: production
"#,
        )
    }

    fn generate_azure(&self, include_deploy: bool) -> String {
        let mut yaml = String::from(
            r#"trigger:
  - main
  - master

pool:
  vmImage: ubuntu-latest

steps:
  - checkout: self
    submodules: recursive

  - script: |
      wget -q https://github.com/foundry-rs/foundry/releases/latest/download/foundry_linux_amd64.tar.gz
      tar -xzf foundry_linux_amd64.tar.gz
      export PATH=$PATH:$(pwd)
      foundryup
    displayName: 'Install Foundry'

  - script: |
      cargo install forge-guard
    displayName: 'Install Forge Guard'

  - script: |
      forge audit --strict
    displayName: 'Run Security Audit'

  - script: |
      forge fuzz --runs 10000
    displayName: 'Run Fuzzing'

  - script: |
      forge invariant --runs 1000
    displayName: 'Run Invariant Tests'

  - script: |
      forge scan --depth 1
    displayName: 'Scan Dependencies'

  - task: PublishBuildArtifacts@1
    inputs:
      pathToPublish: reports/
      artifactName: 'audit-reports'
"#,
        );

        if include_deploy {
            yaml.push_str(
                r#"
  - script: |
      forge audit --strict --production
      forge deploy-safe
    displayName: 'Safe Deploy'                env:
      ETH_RPC_URL: $(ETH_RPC_URL)
"#,
            );
        }

        yaml
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ci_generator_github() {
        let gen = CiGenerator::new("github");
        assert_eq!(gen.filename(), "audit.yml");

        let config = gen.generate(false).unwrap();
        assert!(config.contains("name: Forge Guard Security Check"));
        assert!(config.contains("forge audit --strict"));
        assert!(config.contains("forge fuzz --runs 10000"));
    }

    #[test]
    fn test_ci_generator_github_with_deploy() {
        let gen = CiGenerator::new("github");
        let config = gen.generate(true).unwrap();
        assert!(config.contains("forge deploy-safe"));
        assert!(config.contains("needs: [security-audit]"));
    }

    #[test]
    fn test_ci_generator_gitlab() {
        let gen = CiGenerator::new("gitlab");
        assert_eq!(gen.filename(), ".gitlab-ci.yml");

        let config = gen.generate(false).unwrap();
        assert!(config.contains("forge-guard:"));
        assert!(config.contains("forge audit --strict"));
    }

    #[test]
    fn test_ci_generator_bitbucket() {
        let gen = CiGenerator::new("bitbucket");
        assert_eq!(gen.filename(), "bitbucket-pipelines.yml");

        let config = gen.generate(false).unwrap();
        assert!(config.contains("pipelines:"));
        assert!(config.contains("forge audit --strict"));
    }

    #[test]
    fn test_ci_generator_azure() {
        let gen = CiGenerator::new("azure");
        assert_eq!(gen.filename(), "azure-pipelines.yml");

        let config = gen.generate(false).unwrap();
        assert!(config.contains("vmImage: ubuntu-latest"));
        assert!(config.contains("forge audit --strict"));
    }

    #[test]
    fn test_ci_generator_invalid_platform() {
        let gen = CiGenerator::new("invalid");
        assert!(gen.generate(false).is_err());
    }

    #[test]
    fn test_ci_generator_filenames() {
        assert_eq!(CiGenerator::new("github").filename(), "audit.yml");
        assert_eq!(CiGenerator::new("gitlab").filename(), ".gitlab-ci.yml");
        assert_eq!(
            CiGenerator::new("bitbucket").filename(),
            "bitbucket-pipelines.yml"
        );
        assert_eq!(CiGenerator::new("azure").filename(), "azure-pipelines.yml");
        assert_eq!(CiGenerator::new("unknown").filename(), "ci-config.yml");
    }
}